﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using SDL.Modificators;

namespace SDL
{
    public class Parser
    {
        private const string BlockTagBeginBegin = "##BlockBegin--";
        private const string BlockTagBeginEnd = "##";
        private const string BlockTagEndBegin = "##BlockEnd--";
        private const string BlockTagEndEnd = "##";
        private const string ConditionTagElseBegin = "##Else--";
        private const string ConditionTagElseEnd = "##";
        private const string ConditionTagEndIfBegin = "##EndIf--";
        private const string ConditionTagEndIfEnd = "##";
        private const string ConditionTagIfBegin = "##If--";
        private const string ConditionTagIfEnd = "##";
        private const string ModificatorParamSep = ",";
        private const string ModificatorTag = ":";
        private const string VariableTagBegin = "##";
        private const string VariableTagEnd = "##";
        private readonly Dictionary<string, Parser> _Blocks = new Dictionary<string, Parser>();
        private readonly Hashtable _ErrorMessage = new Hashtable();
        private Hashtable _hstValues;
        private string _ParsedBlock;
        private string _strTemplateBlock;

        public string TemplateBlock
        {
            get { return _strTemplateBlock; }
            set
            {
                _strTemplateBlock = value;
                ParseBlocks();
            }
        }

        public Hashtable Variables
        {
            get { return _hstValues; }
            set { _hstValues = value; }
        }

        public Hashtable ErrorMessage
        {
            get { return _ErrorMessage; }
        }

        public Dictionary<string, Parser> Blocks
        {
            get { return _Blocks; }
        }

        #region Contructors

        public Parser()
        {
            _strTemplateBlock = "";
        }

        public Parser(string FilePath)
        {
            ReadTemplateFromFile(FilePath);
            ParseBlocks();
        }

        public Parser(Hashtable Variables)
        {
            _hstValues = Variables;
        }

        public Parser(string FilePath, Hashtable Variables)
        {
            ReadTemplateFromFile(FilePath);
            _hstValues = Variables;
            ParseBlocks();
        }

        #endregion

        public void SetTemplateFromFile(string FilePath)
        {
            ReadTemplateFromFile(FilePath);
        }

        public void SetTemplate(string templateBlock)
        {
            TemplateBlock = templateBlock;
        }

        public string Parse()
        {
            ParseConditions();
            ParseVariables();
            return _ParsedBlock;
        }

        public string ParseBlock(string BlockName, Hashtable variables)
        {
            if (!_Blocks.ContainsKey(BlockName))
            {
                throw new ArgumentException(String.Format("Could not find Block with Name '{0}'", BlockName));
            }

            _Blocks[BlockName].Variables = variables;
            return _Blocks[BlockName].Parse();
        }

        public bool ParseToFile(string FilePath, bool ReplaceIfExists)
        {
            if (File.Exists(FilePath) && !ReplaceIfExists)
            {
                return false;
            }
            var sr = File.CreateText(FilePath);
            sr.Write(Parse());
            sr.Close();
            return true;
        }

        private void ReadTemplateFromFile(string FilePath)
        {
            if (!File.Exists(FilePath))
            {
                throw new ArgumentException("Template file does not exist.");
            }

            var reader = new StreamReader(FilePath);
            TemplateBlock = reader.ReadToEnd();
            reader.Close();
        }

        private void ParseBlocks()
        {
            var idxCurrent = 0;
            while ((idxCurrent = _strTemplateBlock.IndexOf(BlockTagBeginBegin, idxCurrent)) != -1)
            {
                var idxBlockBeginBegin = idxCurrent;
                idxCurrent += BlockTagBeginBegin.Length;

                var idxBlockBeginEnd = _strTemplateBlock.IndexOf(BlockTagBeginEnd, idxCurrent);
                if (idxBlockBeginEnd == -1) throw new Exception("Could not find BlockTagBeginEnd");

                var BlockName = _strTemplateBlock.Substring(idxCurrent, (idxBlockBeginEnd - idxCurrent));
                idxCurrent = idxBlockBeginEnd + BlockTagBeginEnd.Length;

                var EndBlockStatment = BlockTagEndBegin + BlockName + BlockTagEndEnd;
                var idxBlockEndBegin = _strTemplateBlock.IndexOf(EndBlockStatment, idxCurrent);
                if (idxBlockEndBegin == -1)
                    throw new Exception("Could not find End of Block with name '" + BlockName + "'");

                var block = new Parser
                {
                    TemplateBlock =
                        _strTemplateBlock.Substring(idxCurrent, (idxBlockEndBegin - idxCurrent))
                };
                _Blocks.Add(BlockName, block);

                _strTemplateBlock = _strTemplateBlock.Remove(idxBlockBeginBegin, (idxBlockEndBegin - idxBlockBeginBegin));

                idxCurrent = idxBlockBeginBegin;
            }
        }

        private void ParseConditions()
        {
            var idxPrevious = 0;
            var idxCurrent = 0;
            _ParsedBlock = "";
            while ((idxCurrent = _strTemplateBlock.IndexOf(ConditionTagIfBegin, idxCurrent)) != -1)
            {
                string TrueBlock, FalseBlock;
                bool boolValue;

                var idxIfBegin = idxCurrent;
                idxCurrent += ConditionTagIfBegin.Length;

                var idxIfEnd = _strTemplateBlock.IndexOf(ConditionTagIfEnd, idxCurrent);
                if (idxIfEnd == -1) throw new Exception("Could not find ConditionTagIfEnd");

                var VarName = _strTemplateBlock.Substring(idxCurrent, (idxIfEnd - idxCurrent));

                idxCurrent = idxIfEnd + ConditionTagIfEnd.Length;

                var ElseStatment = ConditionTagElseBegin + VarName + ConditionTagElseEnd;
                var EndIfStatment = ConditionTagEndIfBegin + VarName + ConditionTagEndIfEnd;
                var idxElseBegin = _strTemplateBlock.IndexOf(ElseStatment, idxCurrent);
                var idxEndIfBegin = _strTemplateBlock.IndexOf(EndIfStatment, idxCurrent);
                if (idxElseBegin > idxEndIfBegin)
                    throw new Exception("Condition Else Tag placed after Condition Tag EndIf for '" + VarName + "'");

                if (idxElseBegin != -1)
                {
                    TrueBlock = _strTemplateBlock.Substring(idxCurrent, (idxElseBegin - idxCurrent));
                    FalseBlock = _strTemplateBlock.Substring((idxElseBegin + ElseStatment.Length),
                                                             (idxEndIfBegin - idxElseBegin - ElseStatment.Length));
                }
                else
                {
                    TrueBlock = _strTemplateBlock.Substring(idxCurrent, (idxEndIfBegin - idxCurrent));
                    FalseBlock = "";
                }

                try
                {
                    boolValue = Convert.ToBoolean(_hstValues[VarName]);
                }
                catch
                {
                    boolValue = false;
                }

                var BeforeBlock = _strTemplateBlock.Substring(idxPrevious, (idxIfBegin - idxPrevious));

                if (_hstValues.ContainsKey(VarName) && boolValue)
                {
                    _ParsedBlock += BeforeBlock + TrueBlock.Trim();
                }
                else
                {
                    _ParsedBlock += BeforeBlock + FalseBlock.Trim();
                }

                idxCurrent = idxEndIfBegin + EndIfStatment.Length;
                idxPrevious = idxCurrent;
            }
            _ParsedBlock += _strTemplateBlock.Substring(idxPrevious);
        }

        private void ParseVariables()
        {
            var idxCurrent = 0;
            while ((idxCurrent = _ParsedBlock.IndexOf(VariableTagBegin, idxCurrent)) != -1)
            {
                var idxVarTagEnd = _ParsedBlock.IndexOf(VariableTagEnd, (idxCurrent + VariableTagBegin.Length));
                if (idxVarTagEnd == -1)
                    throw new Exception(String.Format("Index {0}: could not find Variable End Tag", idxCurrent));

                var VarName = _ParsedBlock.Substring((idxCurrent + VariableTagBegin.Length),
                                                        (idxVarTagEnd - idxCurrent - VariableTagBegin.Length));

                var VarParts = VarName.Split(ModificatorTag.ToCharArray());
                VarName = VarParts[0];

                var VarValue = String.Empty;
                if (_hstValues.ContainsKey(VarName) && _hstValues[VarName] != null)
                {
                    VarValue = _hstValues[VarName].ToString();
                }

                for (var i = 1; i < VarParts.Length; i++)
                    ApplyModificator(ref VarValue, VarParts[i]);

                _ParsedBlock = _ParsedBlock.Substring(0, idxCurrent) + VarValue +
                               _ParsedBlock.Substring(idxVarTagEnd + VariableTagEnd.Length);

                idxCurrent += VarValue.Length;
            }
        }

        private static void ApplyModificator(ref string Value, string Modificator)
        {
            string strModificatorName;
            var strParameters = "";
            int idxStartBrackets;
            if ((idxStartBrackets = Modificator.IndexOf("(")) != -1)
            {
                var idxEndBrackets = Modificator.IndexOf(")", idxStartBrackets);
                if (idxEndBrackets == -1)
                {
                    throw new Exception("Incorrect modificator expression");
                }
                strModificatorName = Modificator.Substring(0, idxStartBrackets).ToUpper();
                strParameters = Modificator.Substring(idxStartBrackets + 1, (idxEndBrackets - idxStartBrackets - 1));
            }
            else
            {
                strModificatorName = Modificator.ToUpper();
            }
            var arrParameters = strParameters.Split(ModificatorParamSep.ToCharArray());
            for (var i = 0; i < arrParameters.Length; i++)
                arrParameters[i] = arrParameters[i].Trim();

            try
            {
                var typeModificator = Type.GetType("SDL.Modificators." + strModificatorName);
                if (typeModificator.IsSubclassOf(Type.GetType("SDL.Modificators.Modificator")))
                {
                    var objModificator = (Modificator) Activator.CreateInstance(typeModificator);
                    objModificator.Apply(ref Value, arrParameters);
                }
            }
            catch
            {
                throw new Exception(String.Format("Could not find modificator '{0}'", strModificatorName));
            }
        }
    }
}